#!/bin/bash

__argv0=$0
if [ "$__argv0" == "-bash" -o "$__argv0" == "/bin/bash" ]; then
    __argv0='.'
fi
TESTNAME=`basename $__argv0`
TESTDIR=$(cd `dirname $__argv0` ; pwd)
DIR=$(cd "$(dirname ${BASH_SOURCE[0]})" &>/dev/null && pwd)

COLOR0="\033["
NOCOLOR="\033[0;0m"
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
CYAN="\033[0;36m"
GRAY="\033[0;37m"
BLUE="\033[0;34m"
BLUE_BOLD="\033[0;94m"

# global var to set if test fails. should change to error but never back to
# success.
TEST_FAILED=0
# global var to use for last error msg. like errno and %m.
ERRMSG=""

VENDOR_MELLANOX="0x15b3"


declare -A PCI_MAP
declare -A TRAFFIC_INFO

# set in __test_for_devlink_compat()
devlink_compat=0

# Special variables
__ignore_errors=0
PF_IN_NS=""


function get_mlx_iface() {
    local i
    for i in /sys/class/net/* ; do
        if [ ! -r $i/device/vendor ]; then
            continue
        fi
        t=`cat $i/device/vendor`
        if [ "$t" == "$VENDOR_MELLANOX" ]; then
            . $i/uevent
            NIC=$INTERFACE
            echo "Found Mellanox iface $NIC"
            return
        fi
    done
}

function __test_for_devlink_compat() {
    local pci=$PCI

    if is_bf_host; then
        pci=$BF_PCI
        __devlink_compat_dir="/sys/class/net/\$nic/compat/devlink"
    elif [ -e /sys/kernel/debug/mlx5/$PCI/compat ]; then
        __devlink_compat_dir="/sys/kernel/debug/mlx5/\$pci/compat"
    elif [ -e /sys/class/net/$NIC/compat/devlink ]; then
        __devlink_compat_dir="/sys/class/net/\$nic/compat/devlink"
    fi

    if [ -n "$DEVLINK_COMPAT" ]; then
        devlink_compat=$DEVLINK_COMPAT
    elif __devlink dev param show pci/$pci name flow_steering_mode &>/dev/null ; then
        return
    else
        devlink_compat=${DEVLINK_COMPAT:-1}
    fi
    log "Using devlink compat $devlink_compat"
}

function get_nic_fw() {
    ethtool -i $1 | grep firmware-version | awk {'print $2'}
}

function get_rx_bytes() {
    ethtool -S $1 | grep -E 'rx_bytes_phy|vport_rx_bytes' | awk {'print $2'} | tail -1
}

function get_tx_bytes() {
    ethtool -S $1 | grep -E 'tx_bytes_phy|vport_tx_bytes' | awk {'print $2'} | tail -1
}

function get_rx_pkts() {
    ethtool -S $1 | grep -E 'rx_packets_phy|vport_rx_packets' | awk {'print $2'} | tail -1
}

function get_tx_pkts() {
    ethtool -S $1 | grep -E 'tx_packets_phy|vport_tx_packets' | awk {'print $2'} | tail -1
}

function get_tx_pkts_ns() {
    local ns=$1
    local dev=$2

    ip netns exec $ns cat /sys/class/net/$dev/statistics/tx_packets
}

function get_rx_pkts_ns() {
    local ns=$1
    local dev=$2

    ip netns exec $ns cat /sys/class/net/$dev/statistics/rx_packets
}

function require_cmd() {
    local i
    for i in $@ ; do
        if ! `which $i &>/dev/null` ; then
            fail "Missing required command $i"
        fi
    done
}

function print_key_val() {
    local m=$@
    local c=$CYAN
    awk "{for (i=1; i<=NF; i+=2) print \"$c\"\$i\"$NOCOLOR\", \$(i+1)}" <<< $m | xargs echo
}




function __setup_common() {
    start_test_timestamp

    [ -f /etc/os-release ] && . /etc/os-release
    ANSI_COLOR0="$COLOR0${ANSI_COLOR}m"
    if [ -n "$PRETTY_NAME" ]; then
        kmsg $PRETTY_NAME
        echo -e "${ANSI_COLOR0}$PRETTY_NAME$NOCOLOR"
    fi
    date
    log `uname -nsrp`

    require_interfaces NIC NIC2
    require_cmd lspci ethtool tc bc jq
    fail_if_err
    bf_wrap "ulimit -c unlimited ; sysctl -w fs.suid_dumpable=1"

    sysfs_pci_device=`readlink -f /sys/class/net/$NIC/../../`
    SRIOV_NUMVFS_NIC=$sysfs_pci_device/sriov_numvfs
    sysfs_pci_device2=`readlink -f /sys/class/net/$NIC2/../../`
    SRIOV_NUMVFS_NIC2=$sysfs_pci_device2/sriov_numvfs

    PCI=$(basename `readlink /sys/class/net/$NIC/device`)
    PCI2=$(basename `readlink /sys/class/net/$NIC2/device`)
    PCI_MAP=( [$NIC]=$PCI [$NIC2]=$PCI2 )

    DEVICE=`cat /sys/class/net/$NIC/device/device`
    PCI_DEVICE_NUM=`printf '%d' $DEVICE`
    FW=`get_nic_fw $NIC`

    # __get_device_name
    # __get_pci_device_name
    status="NIC $NIC FW $FW PCI $PCI DEVICE $DEVICE $device_name"
    log $status

}



function is_rh72_kernel() {
    local k=`uname -r`
    if [ "$k" == "3.10.0-327.el7.x86_64" ]; then
        return 0 # true
    fi
    return 1 # false
}



function require_module() {
    local module
    for module in $@ ; do
        modprobe -q $module || fail "Missing module $module"
    done
}

function require_min_kernel_5() {
    local v=`uname -r | cut -d. -f1`
    if [ $v -lt 5 ]; then
        fail "Require minimum kernel 5"
    fi
}

function remote_bf_wrap() {
    # overridden in common-bf.sh
    on_remote "$@"
}

function remote_bf_wrap_exec() {
    # overridden in common-bf.sh
    on_remote_exec "$@"
}

function bf_wrap() {
    # overridden in common-bf.sh
    eval "$@"
}

function bf_wrap_exec() {
    # overridden in common-bf.sh
    eval "$@"
}



function scp2() {
    scp -q -o ConnectTimeout=3 "$@"
}

function ssh2() {
    ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=3 "$@"
    local rc=$?
    if [ $rc -eq 255 ]; then
        debug "SSH connection $1 rc 255"
    fi
    return $rc
}

# Run given code on remote server which provide all function and env vars
function on_remote_exec() {
    [ -z "$REMOTE_SERVER" ] && fail "No remote server configured"
    __on_remote_exec $REMOTE_SERVER "$@"
}

__foo_copied=()
# use a static file to avoid creating endless temp files.
__FOO="/tmp/foo.sh"
function __foo_copy() {
    if [[ "${__foo_copied[@]}" =~ $remote ]]; then
        return
    fi
    local i
    for i in _longopt _parse_help _parse_usage; do unset -f $i ; done
    declare -p PCI_MAP > $__FOO
    declare -p TRAFFIC_INFO >> $__FOO
    set | grep -Ev "^(BASH|SHELLOPTS|UID|EUID|PPID|_OVS_VSCTL|IS_BF_HOST|COVFILE|COVERR)" >> $__FOO
    echo ". /etc/os-release" >> $__FOO
    scp2 $__FOO $remote:/tmp/
    __foo_copied+=($remote)
}

function __on_remote_exec() {
    local remote=$1
    __foo_copy
    ssh2 $remote ". $__FOO; ${@:2}"
}

function on_vm1() {
    [ -z "$NESTED_VM_IP1" ] && fail "No vm ip1 configured"
    __on_remote_clean $NESTED_VM_IP1 "$@"
}

function on_vm2() {
    [ -z "$NESTED_VM_IP2" ] && fail "No vm ip2 configured"
    __on_remote_clean $NESTED_VM_IP2 "$@"
}

function on_remote() {
    [ -z "$REMOTE_SERVER" ] && fail "No remote server configured"
    __on_remote $REMOTE_SERVER "$@"
}

function __on_remote_clean() {
    local remote=$1
    ssh2 $remote "${@:2}"
}

function __on_remote() {
    local remote=$1
    ssh2 $remote "[ -n $CONFIG ] && . $CONFIG ; ${@:2}"
}

function print_remote_test_separator() {
    local remote=$1
    local tmp="## TEST $TESTNAME REMOTE ##"
    local count=${#tmp}
    local sep=$(printf '%*s' $count | tr ' ' '#')
    __on_remote $remote "echo -e \"$sep\n$tmp\n$sep\" >> /dev/kmsg"
}

__USING_REMOTE_SERVER=0
function require_remote_server() {
    if [ -z "$REMOTE_SERVER" ]; then
        fail "Remote server is not configured"
    fi
    if [ -z "$REMOTE_NIC" ]; then
        fail "Remote nic is not configured"
    fi
    log "Remote server $REMOTE_SERVER"
    on_remote true || fail "Remote command failed"
    print_remote_test_separator $REMOTE_SERVER
    __USING_REMOTE_SERVER=1
}

function kmsg() {
    local m=$@
    #if [ -w /dev/kmsg ]; then
    #    echo -e ":test: $m" >>/dev/kmsg
    #fi
}

function title2() {
    local title=${1:-$TESTNAME}
    local tmp="## TEST $title ##"
    local count=${#tmp}
    local sep=$(printf '%*s' $count | tr ' ' '#')

    echo -e "Start test
${YELLOW}${sep}${NOCOLOR}
${YELLOW}${tmp}${NOCOLOR}
${YELLOW}${sep}${NOCOLOR}"

    kmsg "Start test
$sep
$tmp
$sep"
}

function max() {
    echo $(($1>$2?$1:$2))
}

function min() {
    echo $(($1<$2?$1:$2))
}

function ethtool_hw_tc_offload() {
    local nic="$1"
    ethtool -K $nic1 hw-tc-offload on &>/dev/null
}


function debug() {
    echo -e "${CYAN}`get_time_short` | $NOCOLOR$@"
    kmsg "DEBUG: $@"
}

function log() {
    echo $@
    kmsg $@
}

function log_once() {
    local var=$1
    shift
    [ "${!var}" != "" ] && return
    eval $var=1
    log $@
}

function warn() {
    echo -e "${YELLOW}WARNING: $@$NOCOLOR"
    kmsg "WARN: $@"
}

# print error and exit
function fail() {
    local m=${@:-Failed}
    if [ "$__ignore_errors" == "1" ]; then
        log $m
        return
    fi
    TEST_FAILED=1
    kill_all_bgs
    if [ "$m" != "TEST FAILED" ]; then
        m="ERROR: $m"
    fi
    echo -e "${RED}$m$NOCOLOR" >>/dev/stderr
    kmsg "$m"
    if [ "${FREEZE_ON_ERROR}" == "1" ]; then
        debug "Test is freeze on fail - press enter to exit"
        read
    fi
    exit 1
}

function err() {
    local m=${@:-Failed}
    if [ "$__ignore_errors" == "1" ]; then
        log $m
        return
    fi
    TEST_FAILED=1
    m="ERROR: $m"
    echo -e "${RED}$m$NOCOLOR"
    if [ "${FREEZE_ON_ERROR}" == "1" ]; then
        debug "Test is freeze on error - press enter to continue"
        read
    fi
    kmsg "$m"
}

function err_stderr() {
    local m=${@:-Failed}
    TEST_FAILED=1
    m="ERROR: $m"
    echo -e "${RED}$m$NOCOLOR" >> /dev/stderr
    kmsg "$m"
}

function success() {
    local m=${@:-OK}
    echo -e "$GREEN$m$NOCOLOR"
    kmsg $m
}

function success2() {
    local m=$@
    echo -e "${GREEN}OK$NOCOLOR $m"
    kmsg OK
}

function title() {
    echo -e "$CYAN* $@$NOCOLOR"
    kmsg $@
}


function __config_rep() {
    local rep=$1

    bf_wrap "ip address flush dev $rep
             ip link set dev $rep up"
}

function config_vf() {
    local ns=$1
    local vf=$2
    local rep=$3
    local ip=$4 # optional
    local mac=$5 # optional
    local prefix=24

    if [[ "$ip" == *":"* ]]; then
        # ipv6
        prefix=64
    fi

    local info=${mac:+$mac }${ip:+$ip}
    echo "[$ns] VF $vf ${info:+($info)} -> REP $rep"
    ip netns add $ns
    ${mac:+ip link set $vf address $mac}
    ip link set $vf netns $ns || err "Failed to move $vf to ns $ns"
    ${ip:+ip -netns $ns address replace dev $vf $ip/$prefix}
    ip -netns $ns link set $vf up || err "link up $vf failed"

    __config_rep $rep || err "link up $rep failed"
}

function clear_ns_dev() {
    local ns=${1:-ns0}
    local ns_dev=${2:-$VF}
    local rep=${3:-$REP}

    if ip netns ls | grep -q -w $ns; then
        ip -netns $ns link set dev $ns_dev netns 1 2>/dev/null
        ip netns del $ns
    fi
    [ -e /sys/class/net/$rep ] && ip addr flush dev $rep
}

function add_vf_vlan() {
    local ns=$1
    local vf=$2
    local rep=$3
    local ip=$4
    local vlan=$5
    local mac=$6
    local vlan_proto=${7:-802.1Q}
    local prefix=24

    if [[ "$ip" == *":"* ]]; then
        # ipv6
        prefix=64
    fi

    echo "[$ns] $vf.$vlan (${mac:+$mac/}$ip) -> $rep"
    ip -netns $ns link add link $vf name $vf.$vlan type vlan protocol $vlan_proto id $vlan
    ip -netns $ns link set $vf.$vlan address $mac
    ip -netns $ns address replace dev $vf.$vlan $ip/$prefix
    ip -netns $ns link set $vf.$vlan up
}

function add_vf_qinq() {
    local ns=$1
    local vf=$2
    local rep=$3
    local ip=$4
    local vlan_outer=$5
    local vlan_inner=$6
    local mac=$7 # optional
    local prefix=24

    if [[ "$ip" == *":"* ]]; then
        # ipv6
        prefix=64
    fi

    echo "[$ns] $vf.$vlan_outer.$vlan_inner (${mac:+$mac/}$ip) -> $rep"
    ip -netns $ns link add link $vf name $vf.$vlan_outer type vlan protocol 802.1ad id $vlan_outer
    ip -netns $ns link add link $vf.$vlan_outer name $vf.$vlan_outer.$vlan_inner type vlan id $vlan_inner
    ${mac:+ip -netns $ns link set $vf.$vlan_outer.$vlan_inner address $mac}
    ip -netns $ns address replace dev $vf.$vlan_outer.$vlan_inner $ip/$prefix
    ip -netns $ns link set $vf.$vlan_outer up
    ip -netns $ns link set $vf.$vlan_outer.$vlan_inner up
}

function add_vf_mcast() {
    local ns=$1
    local dev=$2
    local ip=$3

    echo "[$ns] $dev mcast grp $ip"
    ip netns exec $ns sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=0 >/dev/null
    ip -netns $ns a add dev $dev $ip/24 autojoin
    ip -netns $ns link set $dev up
}



function count_available_vf() {
    local total_vfs="/sys/class/net/${1}/device/sriov_totalvfs"
    cat "$total_vfs" 2> /dev/null || echo 0
}


function require_interfaces() {
    local i
    local net
    for i in $@; do
        net=${!i}
        [ -z $net ] && fail "Var $i is empty"

        if is_bf_host; then
            on_bf_exec "[ -e /sys/class/net/$net ]" && continue
        fi

        [ ! -e /sys/class/net/$net ] && fail "Cannot find interface $net"
    done
}


function config_sriov() {
    local num=${1:-2}
    local nic=${2:-$NIC}
    local numvfs_sysfs=""

    if [ "$nic" == "$NIC" ]; then
        numvfs_sysfs=$SRIOV_NUMVFS_NIC
    elif [ "$nic" == "$NIC2" ]; then
        numvfs_sysfs=$SRIOV_NUMVFS_NIC2
    fi

    [ -z "$numvfs_sysfs" ] && fail "Invalid numvfs sysfs for $nic"
    [ ! -e "$numvfs_sysfs" ] && fail "Cannot find numvfs sysfs for $nic"

    local cur=`cat $numvfs_sysfs`
    if [ $cur -eq $num ]; then
        return
    fi

    log "Config $num VFs for $nic"
    exec_pf_in_ns echo 0 > $numvfs_sysfs
    exec_pf_in_ns echo $num > $numvfs_sysfs || fail "Failed to config $num VFs on $nic"
    sleep 0.5
    udevadm trigger -c add -s net &>/dev/null
}

function disable_sriov() {
    # a feature in the driver let user set eswitch mode even if sriov is not enabled.
    # then to go back to nic mode need to enable and disable sriov again.
    # another feature is disabling sriov doesn't affect eswitch mode and we could stay in
    # offloads mode.

    # make sure in legacy mode
    enable_legacy $NIC
    enable_legacy $NIC2

    # In old kernels/mlnx ofed versions user could be in eswitch mode while sriov disabled
    # and the only way to get back to nic mode is enable and disable sriov.
    # In those kernels devlink eswitch mode fail if never changed mode
    # or reports switchdev/legacy and user can't tell if nic mode.
    if ofed_ver_lte 5.7; then
        local mode=`get_eswitch_mode 2>/dev/null`
        if [ "$mode" != "" ]; then
            config_sriov 2 $NIC
            config_sriov 2 $NIC2
        fi
    fi

    # disable sriov
    config_sriov 0 $NIC
    config_sriov 0 $NIC2
}

function enable_sriov() {
    config_sriov 2 $NIC
    config_sriov 2 $NIC2
}

function set_macs() {
    local count=$1 # optional
    $SET_MACS $NIC $count
}

function get_vf_pci() {
    local vf=${1:-$VF}

    echo $(basename `readlink /sys/class/net/${vf}/device`)
}

function get_pf_pci() {
    local pci=$PCI

    if is_bf || is_bf_host; then
        pci=$BF_PCI
    fi

    echo "$pci"
}

function get_pf_pci2() {
    local pci=$PCI2

    if is_bf || is_bf_host; then
        pci=$BF_PCI2
    fi

    echo "$pci"
}

function get_pf_nic() {
    local pci=$1

    if [ "$pci" == "$PCI" ]; then
        echo $NIC
    elif [ "$pci" == "$PCI2" ]; then
        echo $NIC2
    elif [ "$pci" == "$BF_PCI" ]; then
        echo $BF_NIC
    elif [ "$pci" == "$BF_PCI2" ]; then
        echo $BF_NIC2
    fi
}



function wait_for_vfs() {
    local nic=$1
    local vfs
    local count=`get_vfs_count $nic`
    local i

    for i in `seq 10`; do
        vfs=`get_bound_vfs_count $nic`
        if [ "$vfs" = "$count" ]; then
            break
        fi
        sleep 1
    done
}


function get_exec_pf_in_ns() {
    get_exec_ns $PF_IN_NS
}

function exec_pf_in_ns() {
    local exec_ns=`get_exec_pf_in_ns`
    $exec_ns $@
}

function get_sw_id() {
    exec_pf_in_ns cat /sys/class/net/$1/phys_switch_id 2>/dev/null
}

function get_port_name() {
    exec_pf_in_ns cat /sys/class/net/$1/phys_port_name 2>/dev/null
}

function get_parent_port_name() {
    local a=`exec_pf_in_ns cat /sys/class/net/$1/phys_port_name 2>/dev/null`
    a=${a%vf*}
    a=${a//pf}
    ((a&=0x7))
    a="p$a"
    echo $a
}

function get_vf() {
    local vfn=$1
    local nic=${2:-$NIC}
    if [ -a /sys/class/net/$nic/device/virtfn$vfn/net ]; then
        echo `ls /sys/class/net/$nic/device/virtfn$vfn/net/`
    else
        fail "Cannot find vf $vfn of $nic"
    fi
}

function get_time() {
    date +"%s"
}

function get_date_time() {
    date +"%Y-%m-%d %H:%M:%S"
}

function get_time_short() {
    date +"%H:%M:%S"
}

function get_ms_time() {
    echo $(($(date +%s%N)/1000000))
}

function start_test_timestamp() {
    # sleep to get a unique timestamp
    sleep 1
    _check_start_ts=`date +"%s"`
    _check_start_ts_full=`get_date_time`
}

function get_test_time_elapsed() {
    local now=`date +"%s"`
    local sec=`echo $now - $_check_start_ts + 1 | bc`
    echo $sec
}

function convertsecs() {
    local t=$1 h m s o=""
    ((h=$t/3600))
    ((m=($t%3600)/60))
    ((s=$t%60))
    [ "$h" != "0" ] && o+="${h}h"
    [ "$m" != "0" ] && o+="${m}m"
    [ "$s" != "0" ] && o+="${s}s"
    echo $o
}

function get_test_time_elapsed_human() {
    convertsecs `get_test_time_elapsed`
}





function verify_have_traffic() {
    local pid=$1
    wait $pid
    local rc=$?
    if [[ $rc -eq 0 ]]; then
        :
    elif [[ $rc -eq 124 ]]; then
        err "Expected to see packets"
    else
        err "Tcpdump failed"
    fi
}

function verify_no_traffic() {
    local pid=$1
    wait $pid
    local rc=$?
    if [[ $rc -eq 124 ]]; then
        :
    elif [[ $rc -eq 0 ]]; then
        err "Didn't expect to see packets"
    else
        err "Tcpdump failed"
    fi
}

function verify_rate() {
    local rate=$1
    local expected_rate=$2

    if [ -z "$rate" ] || [ -z "$expected_rate" ]; then
        err "Missing rate" && return
    fi

    [ "$rate" -eq "$rate" ] 2>/dev/null
    if [ $? -ne 0 ]; then
        err "rate is not numeric"
        return
    fi

    local delta=$((100 * ($expected_rate - $rate)/$expected_rate))
    delta=${delta#-}

    if [ $delta -gt 10 ]; then
        err "delta $delta is greater than 10: rate $rate is over the limit $expected_rate"
    else
        success2 "delta $delta: rate $rate is in the expected limit $expected_rate"
    fi
}

function verify_iperf3_bw() {
    local iperf_out_file=$1
    local rate=$2

    local proto=$(cat $iperf_out_file | jq -r ".start.test_start.protocol | select(.)")

    title "Check iperf3 $proto bandwidth"
    if [ "$proto" = "TCP" ]; then
        local bw=$(cat $iperf_out_file | jq ".end.sum_received.bits_per_second | select(.)")
    else
        # For UDP we have to combine results manually due to iperf3 ver 3.4 bug
        # that always outputs .end.sum_received.bits_per_second as 0:
        # https://github.com/esnet/iperf/issues/754
        #
        # Also, ignore last interval that is usually fraction of a second with
        # wildly varying rate.
        local bw=$(cat $iperf_out_file | jq '[.intervals[] | select(.sum.omitted == false) | .sum.bits_per_second] | .[0:-1] | add / length')
    fi

    bw=`bc <<< $bw/1000/1000`

    verify_rate $bw $rate
}

function wait_for_linkup() {
    local i
    local net=$1
    local state
    local max=12

    for i in `seq $max`; do
        state=`cat /sys/class/net/$net/operstate`
        if [ "$state" = "up" ]; then
            return
        fi
        sleep 1
    done
    warn "Link for $net is not up after $max seconds"
}

function eval2() {
    eval $@
    local err=$?
    test $err != 0 && err "Command failed ($err): $@"
    return $err
}

function fail_if_err() {
    local m=${@:-TEST FAILED}
    if [ $TEST_FAILED != 0 ]; then
        kill_all_bgs
        check_for_errors_log
        check_for_errors_log_remote
        log "runtime: `get_test_time_elapsed_human`"
        fail $m
    fi
}



function __detect_config() {
    local i
    for i in `ls -1d /sys/class/net/*`; do
        cat $i/phys_switch_id &>/dev/null || continue
        local driver=$(basename `readlink $i/device/driver`)
        [ "$driver" == "mlx5_core" ] || continue
        local port_name=`cat $i/phys_port_name 2>/dev/null`
        if [ "$port_name" == "p0" ]; then
            NIC=`basename $i`
            echo "Detected NIC=$NIC"
        elif [ "$port_name" == "p1" ]; then
            NIC2=`basename $i`
            echo "Detected NIC2=$NIC2"
        fi
        [ -n "$NIC" ] && [ -n "$NIC2" ] && break
    done
    is_bf && BF_NIC=$NIC && BF_NIC2=$NIC2
    [ -n "$NIC" ] && [ -n "$NIC2" ] && return 0
    return 1
}

function __load_config() {
    local conf

    # load config if exists
    if [ -n "$CONFIG" ]; then
        if [ -f "$CONFIG" ]; then
            conf=$CONFIG
        elif [ -f "$DIR/$CONFIG" ]; then
            conf=$DIR/$CONFIG
        else
            fail "Config $CONFIG not found"
        fi
    else
        __detect_config || fail "Missing CONFIG"
    fi

    if [ -n "$conf" ]; then
        echo "Loading config $conf"
        . $conf
    fi
}




function get_free_memory() {
    echo $(cat /proc/meminfo | grep MemFree | sed 's/[^0-9]*//g')
}



function config_remote_vxlan() {
    if [ -z "$VXLAN_ID" ] || [ -z "$REMOTE_IP" ]; then
        err "Cannot config remote vxlan"
        return
    fi
    if [ -z "$DSTPORT" ]; then
        DSTPORT=4789
    fi
    on_remote "ip link del vxlan1 &>/dev/null
               ip a flush dev $REMOTE_NIC
               ip link add vxlan1 type vxlan id $VXLAN_ID dev $REMOTE_NIC dstport $DSTPORT $VXLAN_OPTIONS
               ip a add $REMOTE_IP/24 dev $REMOTE_NIC
               ip a add $REMOTE/24 dev vxlan1
               ip l set dev vxlan1 up
               ip l set dev $REMOTE_NIC up
               tc qdisc add dev vxlan1 ingress" || err "Failed to config remote vxlan"
}

function config_remote_geneve() {
    local ext=$1
    if [ -z "$REMOTE_IP" ]; then
        err "Cannot config remote geneve"
        return
    fi

    if [ -z "$geneve_port" ]; then
        local geneve_port=6081
    fi

    local tun="external"
    local set_ip=""

    if [ "$ext" != "external" ] && [ -n "$TUN_ID" ]; then
        tun="id $TUN_ID remote $LOCAL_TUN"
        set_ip="ip a add $REMOTE/24 dev geneve1"
    fi

    on_remote "ip link del geneve1 &>/dev/null
               ip a flush dev $REMOTE_NIC
               ip link add geneve1 type geneve $tun dstport $geneve_port
               ip a add $REMOTE_IP/24 dev $REMOTE_NIC
               $set_ip
               ip l set dev geneve1 up
               ip l set dev $REMOTE_NIC up
               tc qdisc add dev geneve1 ingress" || err "Failed to config remote geneve"
}

function config_remote_geneve_options() {
    local geneve_opts="geneve_opts ffff:80:00001234"

    config_remote_geneve external

    title "Setup remote geneve + opts"
    on_remote "ip link add vm type veth peer name vm_rep
               ifconfig vm $REMOTE/24 up
               ifconfig vm_rep 0 promisc up
               tc qdisc add dev vm_rep ingress
               tc filter add dev vm_rep ingress proto ip flower skip_hw action tunnel_key set src_ip 0.0.0.0 dst_ip $LOCAL_TUN id $TUN_ID dst_port 6081 $geneve_opts pipe action mirred egress redirect dev geneve1
               tc filter add dev vm_rep ingress proto arp flower skip_hw action tunnel_key set src_ip 0.0.0.0 dst_ip $LOCAL_TUN id $TUN_ID dst_port 6081 $geneve_opts pipe action mirred egress redirect dev geneve1
               tc filter add dev geneve1 ingress protocol arp flower skip_hw action tunnel_key unset action mirred egress redirect dev vm_rep
               tc filter add dev geneve1 ingress protocol ip flower skip_hw action tunnel_key unset action mirred egress redirect dev vm_rep"
}

function cleanup_remote_vxlan() {
    on_remote "ip a flush dev $REMOTE_NIC
               ip l del dev vxlan1 &>/dev/null"
}



function ns_wrap() {
    local cmd=$1
    local ns=$2

    if [[ -n $ns ]]; then
        cmd="ip netns exec $ns $cmd"
    fi
    echo $cmd
}

function ns_exec() {
    local ns=$1
    shift
    local cmd=$@

    if [[ -n $ns ]]; then
        cmd="ip netns exec $ns $cmd"
    fi
    eval $cmd
}

function get_exec_ns() {
    local ns=$1
    local exec_ns

    if [ -n "$ns" ]; then
        exec_ns="ip netns exec $ns"
    fi

    echo $exec_ns
}

function is_ipv6() {
    local ip=$1

    if [[ "$ip" == *":"* ]]; then
        return 0
    fi

    return 1
}

function create_vlan_interface() {
    local parent_int=$1
    local vlan_int=$2
    local vlan_id=$3

    ip link add link $parent_int name $vlan_int type vlan id $vlan_id
}

function get_ovs_id() {
    cat /etc/openvswitch/system-id.conf 2>/dev/null || fail "Got empty ovs id"
}




function grace_period_after_pci_reset() {
    local sleep=70
    local output=`devlink health show pci/${PCI} reporter fw_fatal 2>/dev/null | grep -Eo "grace_period [0-9]+" | awk {'print $2'}`

    if [ -n "$output" ]; then
        local devlink_grace=$((output/1000))

        if [[ $devlink_grace -gt $sleep ]]; then
            sleep=$devlink_grace
        fi
    fi

    # the grace period should start after pci recovers.
    timeout 20 grep -m1 "health recovery succeeded" <(dmesg -W)

    echo "Waiting $sleep sec for pci reset grace period"
    sleep $sleep
}

function pci_reset() {
    echo 1 > /sys/bus/pci/devices/$PCI/reset
    grace_period_after_pci_reset
}


function __test_help() {
    echo "To run a test export a config and run the test script as so:"
    echo
    echo "export CONFIG=/path/to/config.sh"
    echo "$TESTDIR/$TESTNAME"
    echo
    echo "Available exports:"
    echo
    echo "DEVLINK_COMPAT=0|1|2          - Force to use or not use ofed devlink compat."
    echo "KMEMLEAK_SCAN=1               - Do kmemleak scan at the end of the test."
    echo "RELOAD_DRIVER=1               - Reload driver at the end of the test."
    echo "USE_OPENIBD=1                 - Use openibd service script (i.e. MLNX OFED) to reload modules. default=1"
    echo "FREEZE_ON_ERROR=1             - Pause test on each error."
    echo "ENABLE_OVS_DEBUG=1            - Set ovs debug level."
    echo "ENABLE_OVS_MEMORY=1           - Print a report of OVS memory usage."
    echo "CLEAR_OVS_LOG=1               - Clear ovs log at the start of the test."
    echo "VALGRIND_OPENVSWITCH=1        - Start openvswitch with valgrind."
    echo "SKIP_OVS_LOG_DUMP=0           - Skip ovs log dump on failure."
    echo "ENABLE_SOS_COLLECTOR=0        - Collect sos reports."
    exit 0
}

function __common_main() {
    if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
        __test_help
    fi
    [ "$__COMMON_SH" == 1 ] && return
    __COMMON_SH=1
    if [ "X${NO_TITLE}" == "X" ]; then
        title2 $TESTNAME
    fi
    __load_config
    warn_if_redmine_bug_is_open
    trap __trapped_int_cleanup INT
    __setup_common
    __setup_clean
}

function __common_help() {
    echo "Evaluate a script as:"
    echo
    echo "bash $TESTNAME [script]"
    echo
    echo "Example: bash $TESTNAME \"enable_switchdev\""
    exit 0
}

function __common_eval() {
    if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ "$*" == "" ]; then
        __common_help
    fi
    NO_TITLE=1
    __common_main
    log "Evaluate: $@"
    eval $@
    exit $?
}

# common script executed directly. evaluate user input.
if [[ "$TESTNAME" == "common.sh" ]]; then
    __common_eval $@
fi

# script included from bash console. do nothing
if [ "$TESTNAME" == "." ]; then
    return
fi

# execute normally
__common_main $@
